package core.database;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import models.games.Game;
import models.util.Partita;
import core.User;

/**
 * Classe di utilità per la gestione delle query SQL necessarie alla
 * manipolazione dei dati presenti nel database m-health creato appositamente
 * con MySQL Workbench
 */
public class DataBaseManager {
	protected static String driver = "com.mysql.jdbc.Driver";
	protected static String url = "jdbc:mysql://localhost:3306/m-health";
	protected static String password;
	protected static Connection conn;
	protected static Statement statement;
	private static PreparedStatement addUserStatement;
	private static ResultSet selectResultSet;
	private static String fileLocation;

	public DataBaseManager() {
	}

	/**
	 * Crea la connessione con il database a partire da url user e psw passati
	 * come parametro.
	 * 
	 * @param url
	 * @param user
	 * @param psw
	 * @throws SQLException
	 */
	public static void connect(String user, String psw) throws SQLException {
		String DBUserName = user;
		String DBPassword = psw;
		System.setProperty("jdbc.driveresultSet", driver);
		conn = DriverManager.getConnection(url, DBUserName, DBPassword);
		statement = conn.createStatement();
		addUserStatement = conn.prepareStatement("INSERT into Utenti (username,password,domandaSegreta,rispostaSegreta) VALUES (?,?,?,?)");
	}

	public static void connectDefaultFile() throws IOException, SQLException {
		BufferedReader br = null;
		try {
			// Gestione dell'assenza del file
			File f = new File("LocationDatabaseAccessFile.txt");
			if (!f.exists()) {
				f.createNewFile();
				writeFile(f, "C:\\Utilità_MHealth\\AccessoDatabase.txt");
			}
			br = new BufferedReader(new FileReader(f));
			// Gestione del file LocationDatabaseAccessFile.txt vuoto.
			String readLine = br.readLine();
			if (readLine == null) {
				fileLocation = "C:\\Utilità_MHealth\\AccessoDatabase.txt";
			} else
				fileLocation = readLine;
			br.close();
			System.out.println("Lettura primo OK");
		} catch (IOException e) {
			System.out.println("Eccezione lettura primo file");
			br.close();
		}

		String DBUserName = "";
		String DBPassword = "";
		br = new BufferedReader(new FileReader(fileLocation));
		DBUserName = br.readLine();
		DBPassword = br.readLine();
		System.setProperty("jdbc.driveresultSet", driver);
		conn = DriverManager.getConnection(url, DBUserName, DBPassword);
		statement = conn.createStatement();
		addUserStatement = conn.prepareStatement("INSERT into Utenti (username,password,domandaSegreta,rispostaSegreta) VALUES (?,?,?,?)");
		br.close();

	}

	public static void writeFile(File f, String line) {
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(f));
			writer.write(line);
			writer.close();
		} catch (Exception e) {
			System.out.println("Errore scrittura");
			e.printStackTrace();
		}

	}

	public static boolean connectWithFile(File userFile) throws SQLException {
		String backupOldLocation = fileLocation; // si effettua un backup per non perdere la vecchia posizione
		fileLocation = userFile.getAbsolutePath();
		// Viene salvato il nuovo percorso nel file di testo
		// LocationDatabaseAccessFile.txt nella directory di progetto
		try {
			File oldFile = new File("LocationDatabaseAccessFile.txt");
			oldFile.delete(); // elimino il vecchio file
			File newFile = new File("LocationDatabaseAccessFile.txt"); // creo un file vuoto con lo stesso nome											
			newFile.createNewFile();
			writeFile(newFile, fileLocation);
		} catch (IOException e) {
			 System.out.println("Errore scrittura");
		}
		try {
			connectDefaultFile();
			return true;
		} catch (Exception e) {
			fileLocation = backupOldLocation; // viene ripristinato il vecchio path e aggiornato il file
			File oldFile = new File("LocationDatabaseAccessFile.txt");
			oldFile.delete();
			File newFile = new File("LocationDatabaseAccessFile.txt");
			try {
				newFile.createNewFile();
				writeFile(newFile, fileLocation);
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			return false;
		}
	}

	/**
	 * 
	 * @param a
	 * @return Media attualmente memorizzata nel database
	 * @throws SQLException
	 */
	public static int getAverage(int idArea, User user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("valore","Media","area = " + idArea + " && utente = '" + user.getUsername()+ "'"));
		if (!selectResultSet.next())
			return -1;
		// throw new IllegalArgumentException("Valore non presente");
		return selectResultSet.getInt(1);
	}

	/**
	 * Aggiorna nella tabella media il "valore" e la "dataValore" con la nuova
	 * media e la data corrente
	 * 
	 * @param a
	 * @param avg
	 *            nuovo valore
	 * @throws SQLException
	 */
	public static void addAverage(int idArea, User user, int avg)
			throws SQLException {
		statement.execute(buildInsertQuery("Media", "", "" + idArea + ",'"
				+ user.getUsername() + "'," + avg + ",'"
				+ getStringCurrentDay() + "'"));
	}

	public static void setAverage(int idArea, User user, int avg)
			throws SQLException {
		statement.execute(buildUpdateQuery("Media", "valore", "" + avg,"utente = '" + user.getUsername() + "' && " + "area = "+ idArea));
		statement.execute(buildUpdateQuery("media", "dataValore", "'"+ getStringCurrentDay() + "'","utente = '" + user.getUsername() + "' && " + "area = "+ idArea));
	}

	private static String getStringCurrentDay() {
		long date = System.currentTimeMillis(); // crea la data corrente in millisecondi
		java.util.Date currentDate = new java.util.Date(date); // trasforma la data corrente nell'oggetto Date di java.util
		DateFormat dg = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		return dg.format(currentDate);
	}

	public static Timestamp getAverageDate(int idArea, User user)
			throws SQLException {
		System.out.println(buildSelectQuery("dataValore", "media", "area = "
				+ idArea + " && utente = '" + user.getUsername() + "'"));
		selectResultSet = statement.executeQuery(buildSelectQuery("dataValore",
				"media",
				"area = " + idArea + " && utente = '" + user.getUsername()
						+ "'"));
		if (!selectResultSet.next())
			throw new IllegalArgumentException(
					"Valore non presente -> getAverageDate di DatabaseManager");
		return selectResultSet.getTimestamp(1);
	}

	public static int[] getAreaScoreSince(int idArea, User user,
			Timestamp dateTime) throws SQLException {
		String stringDate = dateTime.toString();
		selectResultSet = statement.executeQuery(buildSelectQuery("Punteggio",
				"partite",
				"(gioco = " + getPossibleGame(idArea) + " || gioco = "
						+ (getPossibleGame(idArea) + 1) + ") && utente = '"
						+ user.getUsername() + "' && dataPartita >'"
						+ stringDate.substring(0, stringDate.length() - 2)
						+ "'"));
		List<Integer> average = new ArrayList<Integer>();
		while (selectResultSet.next()) {
			average.add(selectResultSet.getInt(1));
		}
		if (average.size() == 0)
			return null; // il resultSet era vuoto
		int[] averages = new int[average.size()];
		for (int i = 0; i < averages.length; i++) {
			averages[i] = average.get(i);
		}
		return averages;
	}

	private static int getPossibleGame(int idArea) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("idGioco",
				"giochi", "area = " + idArea));
		selectResultSet.next();
		return selectResultSet.getInt(1);
	}

	/**
	 * @return record dello user nel gioco richiesto (punteggio massimo in un
	 *         gioco)
	 * @throws SQLException
	 */
	public static int getRecord(User user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery(
				"MAX(Punteggio)", "Partite", "utente = '" + user.getUsername()
						+ "'"));
		if (!selectResultSet.next())
			throw new IllegalArgumentException();
		return selectResultSet.getInt(1);
	}


	public static String getCryptedPassword(String user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("password",
				"utenti", "username = '" + user + "'"));
		if (selectResultSet.next())
			return selectResultSet.getString(1);
		throw new IllegalArgumentException("Utente non trovato");
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * 
	 * @throws SQLException
	 * @generated
	 * @ordered
	 */

	public static String getSecretQuestion(String user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery(
				"domandaSegreta", "Utenti", "username = '" + user + "'"));
		if (selectResultSet.next())
			return selectResultSet.getString(1);
		throw new IllegalStateException("Utente non trovato");
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * 
	 * @throws SQLException
	 * @generated
	 * @ordered
	 */

	public static String getSecretAnswer(String user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery(
				"rispostaSegreta", "Utenti", "username = '" + user + "'"));
		if (selectResultSet.next())
			return selectResultSet.getString(1);
		throw new IllegalStateException("Utente non trovato");
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * 
	 * @throws SQLException
	 * @generated
	 * @ordered
	 */

	public static int getGlobalRecord(int game) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("record",
				"Giochi", "idGioco = " + game));
		if (!selectResultSet.next())
			return -1;
		return selectResultSet.getInt(1);
	}

	/**
	 * Richiede le partite giocate dall'utente corrente nella data richiesta
	 * 
	 * @param d
	 * @return
	 * @throws SQLException
	 */
	public static String[] getChronogy(String requestDate,String user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("*","partite", "dataPartita < '" + nextDay(requestDate)+ "' && dataPartita >'" + previousDay(requestDate)
						+ "' && utente = '" + user + "'"));
		return convertResultSet(selectResultSet);
	}

	private static String previousDay(String requestDate) {
		StringTokenizer tk = new StringTokenizer(requestDate, "-");
		String yearToken = tk.nextToken(); // non viene gestito l'anno
		String monthToken = tk.nextToken();
		String dayToken = tk.nextToken();
		int month = Integer.parseInt(monthToken);
		int day = Integer.parseInt(dayToken);
		if (day == 01) {
			if (month == 3) {
				day = 28;
			} else if (month == 2 || month == 4 || month == 6 || month == 8	|| month == 9 || month == 11) {
				day = 31;
			} else {
				day = 30;
			}
			month--;
		} else
			day--;
		return yearToken + "-" + month + "-" + day;
	}

	private static String nextDay(String requestDate) {
		StringTokenizer tk = new StringTokenizer(requestDate, "-");
		String yearToken = tk.nextToken(); // non viene gestito l'anno
		String monthToken = tk.nextToken();
		String dayToken = tk.nextToken();
		int month = Integer.parseInt(monthToken);
		int day = Integer.parseInt(dayToken);
		if (month == 2) {
			if (day == 28) {
				day = 01;
				month++;
			} else
				day++;
		} else if (month == 4 || month == 6 || month == 9 || month == 11) {
			if (day == 30) {
				day = 01;
				month++;
			} else
				day++;
		} else {
			if (day == 31) {
				day = 01;
				month++;
			} else
				day++;
		}
		return yearToken + "-" + month + "-" + day;
	}

	/**
	 * Metodo per convertire un resultSet in un array di stringhe.
	 * 
	 * @param resultSet
	 * @return
	 * @throws SQLException
	 */
	private static String[] convertResultSet(ResultSet resultSet)
			throws SQLException {
		LinkedList<String> listOfGame = new LinkedList<String>(); // Uso una lista perchè non so quanto sarà grande il resultSet
		@SuppressWarnings("unused")
		DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		while (resultSet.next()) {
			listOfGame.add(getGameName(resultSet.getInt(3)) + " "+ resultSet.getTimestamp(4).toString().substring(0, 19)+ " Punteggio: " + resultSet.getInt(5));
		}
		if (listOfGame.size() == 0) { // resultSet vuoto
			return null;
		}
		String[] stringResultSet = new String[listOfGame.size()];
		for (int i = 0; i < stringResultSet.length; i++) {
			stringResultSet[i] = listOfGame.get(i);
		}
		return stringResultSet;
	}

	public static String getGameName(int key) throws SQLException {
		Statement state = conn.createStatement();
		selectResultSet = state.executeQuery(buildSelectQuery("gioco",
				"giochi", "idGioco = " + key));
		if (selectResultSet.next() == false){
			System.out.println("ERROR KEY" +key);
			throw new IllegalArgumentException(); // se il resultset è vuoto
													// vuol dire che il valore
													// di key è errato
		}
			return selectResultSet.getString(1);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * 
	 * @throws SQLException
	 * @generated
	 * @ordered
	 */

	public static int getLevel(User user) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("livello",
				"utenti", "username = '" + user.getUsername() + "'"));
		if (!selectResultSet.next())
			throw new IllegalArgumentException("Utente non trovato");
		return selectResultSet.getInt(1);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * 
	 * @throws SQLException
	 * @generated
	 * @ordered
	 */

	public static int getLastScore(User user, Game game) throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery(
				"Punteggio, Max(dataPartite)",
				"Partite",
				"utente = '" + user.getUsername() + "' && gioco = "
						+ game.getGameKey()));
		if (selectResultSet.next())
			return selectResultSet.getInt(0);
		throw new IllegalArgumentException("Utente non trovato");
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * 
	 * @throws SQLException
	 * @generated
	 * @ordered
	 */

	public static void updateDatabaseLevel(User user, int newLevel)
			throws SQLException {
		statement.execute(buildUpdateQuery("utenti", "livello", "" + newLevel,
				"username = '" + user.getUsername() + "'"));
	}

	public static void updateDatabaseExperience(User user) {
		try {
			statement.execute(buildUpdateQuery("utenti", "puntiEsperienza", ""
					+ "" + user.getExperience(),
					"username = '" + user.getUsername() + "'"));
		} catch (SQLException e) {
			System.out.println(e + "da updateLevel in DataBaseManager");
			e.printStackTrace();
		}
	}

	/**
	 * Aggiorna il record globale del gioco. Questo metodo è invocato al termine
	 * di una partita solo se il punteggio dell'utente è maggiore del record
	 * globale.
	 * 
	 * @throws SQLException
	 */
	public static void updateGlobalRecord(Game game, int score)
			throws SQLException {
		statement.execute(buildUpdateQuery("giochi", "record", "" + score,
				"idGioco = " + game.getGameKey()));
	}

	/**
	 * Aggiunge le informazioni di una partita appena concusa. Se il punteggio
	 * effettuato è superiore al record globale lo aggiorna.
	 */
	public static void addGame(User user, Game game, int score) {
		try {
			statement.execute(buildInsertQuery("Partite",
					"utente,gioco,dataPartita,punteggio",
					"'" + user.getUsername() + "'," + game.getGameKey() + ",'"
							+ getStringCurrentDay() + "'," + score));
			updateDatabaseExperience(user);
			int levelUser = user.getLevel();
			if (levelUser > getLevel(user))
				updateDatabaseLevel(user, levelUser);
			if (score > getGlobalRecord(game.getGameKey()))
				updateGlobalRecord(game, score);
		} catch (SQLException e) {
			System.out.println(e + " in addPartite -> DataBaseManager");
			e.printStackTrace();
		}
	}

	/**
	 * Aggiunge un utente al database a partire da uno user presente in memoria
	 * (creato prima di utilizzare questo metodo);
	 * 
	 * @param user
	 * @return True se l'inserimento è andato a buon fine. False se esiste già
	 *         una tupla nel database con lo stesso username di user
	 * @throws SQLException
	 */
	public static boolean addUser(User user) throws SQLException {
		System.out.println("ciao da addUser");
		addUserStatement.setString(1, user.getUsername());
		addUserStatement.setString(2, user.getPassword());
		addUserStatement.setString(3, user.getSecretQuestion());
		addUserStatement.setString(4, user.getSecretAnswer());
		boolean status = true;
		try {
			addUserStatement.execute();
			for (int i = 1; i <= 5; i++)
				addAverage(i, user, -1);
		} catch (SQLException e) {
			status = false;
			System.out.println(e + "in DataBaseManager.java");
			e.printStackTrace();
		}
		return status;
	}

	public static boolean verifyUserExistence(String username)
			throws SQLException {
		selectResultSet = statement.executeQuery(buildSelectQuery("*",
				"utenti", "username = '" + username + "'"));
		return selectResultSet.next();
	}

	public static boolean verifyLogin(String username, String password)
			throws SQLException {
		selectResultSet = statement
				.executeQuery(buildSelectQuery("username,password", "utenti",
						"username = '" + username + "'"));
		// se l'utente non esiste nel database il selectResultSet sarà vuoto
		if (!selectResultSet.next())
			return false;
		return selectResultSet.getString(2).equals(password);
	}

	/**
	 * Crea un oggetto in memoria dell'utente a partire dal database
	 * 
	 * @param username
	 * @param password
	 * @return
	 * @throws SQLException
	 */
	public static User createUser(String username, String password)
			throws SQLException {
		if (!verifyLogin(username, password))
			throw new IllegalArgumentException(
					"Username inesistente o password errata");
		selectResultSet = statement.executeQuery(buildSelectQuery("*",
				"utenti", "username = '" + username + "'"));
		selectResultSet.next();
		return new User(selectResultSet.getString(1),
				selectResultSet.getString(2), selectResultSet.getString(3),
				selectResultSet.getString(4), selectResultSet.getInt(5),
				selectResultSet.getInt(6));
	}

	private static String buildSelectQuery(String columns, String table,
			String verifyString) {
		return "SELECT " + columns + " FROM " + table + " WHERE "
				+ verifyString;
	}

	private static String buildInsertQuery(String table, String columns,
			String values) {
		return "INSERT INTO " + table + " (" + columns + ") VALUES (" + values
				+ ")";
	}

	private static String buildUpdateQuery(String table, String update,
			String newValue, String verifyString) {
		return "UPDATE " + table + " SET " + update + " = " + newValue
				+ " WHERE " + verifyString;
	}

	public static int getNumGames() throws SQLException {
		selectResultSet = statement.executeQuery("SELECT COUNT(*) FROM Giochi");
		if (!selectResultSet.next())
			throw new IllegalArgumentException("Valore non presente");
		return selectResultSet.getInt(1);
	}

	public static Partita[] getBestScores(int idGioco) throws SQLException {
		String query = "SELECT utente, Punteggio FROM Partita WHERE gioco='idGioco' LIMIT 10 ORDER BY Punteggio DESC";
		selectResultSet = statement.executeQuery(query);
		final int utenteColumn = 1;
		final int punteggioColumn = 2;
		Partita[] array = new Partita[10];
		int i = 0;
		while (selectResultSet.next()) {
			Partita tmp = new Partita();
			tmp.setUserName(selectResultSet.getString(utenteColumn));
			tmp.setPunteggio(selectResultSet.getInt(punteggioColumn));
			array[i++] = tmp;
		}
		return array;
	}
	
	/**
	 * Interrompe la connessione con il database
	 * @throws SQLException
	 */
	public static void closeConnection() throws SQLException {
		statement.close();
		conn.close();
	}

}
